/* 
 * GenericDataBaseIntegrationImpl.java
 * 
 * Data de criação: 11/02/2010
 * 
 * O2SOLUTIONS - SOLUÇÕES EM TECNOLOGIA DA INFORMAÇÃO. 
 *
 */
package br.com.o2solutions.core.integration.repository.impl.hibernate;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;

import br.com.o2solutions.core.exception.SystemException;
import br.com.o2solutions.core.integration.repository.DataBaseIntegration;
import br.com.o2solutions.util.assertion.Assert;
import br.com.o2solutions.util.reflection.IdUtil;

/**
 * Classe responsável por implementar as operações comuns aos objetos de acesso
 * a dados (Entidades)
 * 
 * @author osvaldeir
 * @since 11/02/2010
 * 
 */
@SuppressWarnings("unchecked")
public class GenericDataBaseIntegrationHibernateImpl<T> extends JpaDaoSupport
		implements DataBaseIntegration<T> {

	protected Class<T> type;

	private Logger log = LoggerFactory.getLogger(this.getClass());

	public GenericDataBaseIntegrationHibernateImpl(Class<T> type) {
		this.type = type;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Integer executeUpdate(final String namedQuery,
			final Object... arguments) {
		return getJpaTemplate().execute(new JpaCallback<Integer>() {

			@Override
			public Integer doInJpa(EntityManager em)
					throws PersistenceException {
				final Query query = em.createNamedQuery(namedQuery);
				if (arguments != null && arguments.length > 0) {
					for (int i = 0; i < arguments.length; i++) {
						query.setParameter(i, arguments[i]);
					}
				}
				return query.executeUpdate();
			}

		}, true);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Integer executeNativeUpdate(final String namedQuery,
			final Object... arguments) {
		return getJpaTemplate().execute(new JpaCallback<Integer>() {
			@Override
			public Integer doInJpa(EntityManager em)
			throws PersistenceException {
				final Query query = em.createNativeQuery(namedQuery);
				if (arguments != null && arguments.length > 0) {
					for (int i = 0; i < arguments.length; i++) {
						query.setParameter(i, arguments[i]);
					}
				}
				return query.executeUpdate();
			}
			
		}, true);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<T> findAll() {
		return getJpaTemplate().execute(new JpaCallback<List<T>>() {
			@Override
			public List<T> doInJpa(EntityManager em)
					throws PersistenceException {
				Query query = getFindAllNamedQuery(type);
				if (query == null) {
					String newQuery = "FROM ".concat(type.getSimpleName());
					query = em.createQuery(newQuery);
				}
				return query.getResultList();
			}
		}, true);
	}

	/*
	 * Obtém a query da @NamedQuery de nome findAll, se existir
	 * 
	 * @param clazz
	 * 
	 * @return string Nome da namedquery
	 */
	private Query getFindAllNamedQuery(final Class<?> clazz) {
		return getJpaTemplate().execute(new JpaCallback<Query>() {
			@Override
			public Query doInJpa(EntityManager em) throws PersistenceException {
				Query result = null;
				String queryName = clazz.getSimpleName() + ".findAll";
				try {
					result = em.createNamedQuery(queryName);
				} catch (Exception e) {
					log.warn("NamedQuery ".concat(queryName).concat(
							" nao encontrada"));
				}
				return result;
			}
		}, true);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<T> findByNamedQuery(final String namedQuery,
			final Object... arguments) {
		return getJpaTemplate().execute(new JpaCallback<List<T>>() {
			@Override
			public List<T> doInJpa(EntityManager em)
					throws PersistenceException {
				final Query query = em.createNamedQuery(namedQuery);
				if (arguments != null && arguments.length > 0) {
					for (int i = 0; i < arguments.length; i++) {
						query.setParameter(i + 1, arguments[i]);
					}
				}
				return query.getResultList();
			}
		}, true);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<T> findByMappedNamedQuery(final String namedQuery,
			final Map<String, Object> mappedArguments) {
		return getJpaTemplate().execute(new JpaCallback<List<T>>() {
			@Override
			public List<T> doInJpa(EntityManager em)
					throws PersistenceException {
				final Query query = em.createNamedQuery(namedQuery);
				String message = "Os parametros da query não podem ser nulos. Utilize o método findByNamedQuery";
				Assert.notNull(mappedArguments,message);
				Assert.isTrue(!mappedArguments.isEmpty(),message);
				Iterator<String> iterator = mappedArguments.keySet().iterator();
				while (iterator.hasNext()) {
					String key = iterator.next();
					query.setParameter(key, mappedArguments.get(key));
				}
				return query.getResultList();
			}
		}, true);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void flush() {
		getJpaTemplate().flush();
	}

	/**
	 * {@inheritDoc}
	 */

	@Override
	public void remove(T entity) {
		Object id = IdUtil.getId(entity);
		Assert.notNull(id, new SystemException(
				"O id da entidade a ser removida não pode ser nulo"));
		getJpaTemplate().remove(
				getJpaTemplate().getReference(entity.getClass(), id));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void saveOrUpdate(T entity) {
		Object id = IdUtil.getId(entity);
		if (id != null) {
			getJpaTemplate().merge(entity);
		} else {
			getJpaTemplate().persist(entity);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public T findById(Serializable id) {
		return getJpaTemplate().find(type, id);
	}

	/**
	 * Retorna o {@link Criteria} para a Entidade
	 * 
	 * @param clazz
	 * @return Criteria
	 */
	protected Criteria getCriteria() {
		return getJpaTemplate().execute(new JpaCallback<Criteria>() {
			@Override
			public Criteria doInJpa(EntityManager em)
					throws PersistenceException {
				Session session = (Session) em.getDelegate();
				Criteria criteria = session.createCriteria(type);
				return criteria;
			}
		}, true);
	}

	/**
	 * {@inheritDoc}
	 */
	public List<T> findByCriterio(T entity) {
		Criteria criteria = getCriteria();
		criteria.add(Example.create(entity).enableLike().excludeZeroes()
				.ignoreCase());
		return criteria.list();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void save(T entity) {
		getJpaTemplate().persist(entity);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void update(T entity) {
		if (IdUtil.getId(entity) == null) {
			throw new SystemException(
					"A entidade a ser atualizada não pode ser nula");
		}
		getJpaTemplate().merge(entity);
	}

	/**
	 * {@inheritDoc}
	 * 
	 */
	@Override
	public void refresh(T entity) {
		getJpaTemplate().refresh(entity);

	}

	/**
	 * Executa um método retornando um tipo genérico de acordo com a
	 * implementação do callback relacionado.
	 * 
	 * @param jpaCallback
	 * @return
	 */
	public T execute(JpaCallback<T> jpaCallback) {
		return getJpaTemplate().execute(jpaCallback);
	}
}
